#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "SPI.h"
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

#ifndef STASSID
#define STASSID "ESMS"
#define STAPSK "ESMSESMS"
#endif

// button pins
#define key0 D0 //  key0
#define key1 D6 //  key1

// Screen ppins
#define TFT_DC D1
#define TFT_CS D8
#define TFT_MOSI D7
#define TFT_CLK D5
#define TFT_RST D2
#define TFT_MISO D6

// State define
#define WIFI_DOWN 0
#define WIFI_UP 1
#define WIFI_CONNECT 2

unsigned int localPort = 8888; // local port to listen on

//定义聊天框的全局信息
int line_max_length = 30; //每行最多的字数
int line_show_num = 28;   //最多显示多少行信息，多的要隐藏
// int line_show_num = 20; //使用80MHZ显示行数少避免崩溃
int char_width = 6; //一个字符的宽度
// int char_height = 14; //使用80MHZ显示行数少避免崩溃
int char_height = 10;        //一个字符的高度
String boardName = "Client"; //名字是客户端
// String boardName = "Server";//名字叫服务器
int total_msg_height = 0;
int OPTION = 4;

std::vector<int> chat_modes;
std::vector<String> messages;

// buffers for receiving and sending data
char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1]; // buffer to hold incoming
                                               // packet,
char ReplyBuffer[] = "acknowledged\r\n";       // a string to send back

// TFT screen
Adafruit_ILI9341 tft =
    Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST);

// esp8266 wifi UDP
WiFiUDP Udp;
char remoteIP[] = "192.168.4.1";
unsigned int remotePort = 8888;

// state define
int state = WIFI_DOWN;
int preState = state;

// constants won't change:
const long interval = 1000;

// variables
int ledState = LOW;
unsigned long previousMillis = 0;

void setup() {
  Serial.begin(115200);

  // initialize the LED pin as an output:
  pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output

  // initialize the pushbutton pin as an input:
  pinMode(key0, INPUT);
  pinMode(key1, INPUT);

  tft.begin();
  initScreen();

  WiFi.mode(WIFI_STA);
  WiFi.begin(STASSID, STAPSK);

  state = WIFI_UP;

  Serial.printf("UDP server on port %d\n", localPort);
  Udp.begin(localPort);
}

//初始化UI界面
void initScreen() {
  //设置背景颜色
  tft.fillScreen(ILI9341_BLACK);
  yield();

  //设置状态灯
  tft.fillCircle(15, 10, 5, ILI9341_RED);

  //设置状态信息
  tft.setCursor(30, 6);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.println("No Connection");
  tft.setCursor(tft.width() - boardName.length() * char_width - 10, 8);
  tft.println(boardName);
  //设置聊天框
  tft.drawRect(5, 20, tft.width() - 10, tft.height() - 30, ILI9341_WHITE);
}

/*
 * 改变连接状态
 *
 * con_mode:
 *  0: 未连接，亮红灯
 *  1: 成功连接，亮绿灯
 *  2: 连接错误，亮黄灯
 *
 * text：
 *  连接信息展示，未连接显示No
 * Connection，连接成功显示对方ip，连接错误显示对应的错误信息
 */
void changeConState(int con_mode, String text) {
  //画个框框盖住字，省去刷新全屏的时间
  tft.fillRect(30, 6, tft.width() - 30 - boardName.length() * char_width - 10,
               char_height, ILI9341_BLACK);
  //
  switch (con_mode) {
  case WIFI_DOWN:
    tft.fillCircle(15, 10, 5, ILI9341_RED);
    break;
  case WIFI_CONNECT:
    tft.fillCircle(15, 10, 5, ILI9341_GREEN);
    break;
  case WIFI_UP:
    tft.fillCircle(15, 10, 5, ILI9341_YELLOW);
    break;
  }
  tft.setCursor(30, 8);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.println(text.c_str());
}

/*
 * 大幅度加快刷新速度
 * isRefresh：
 *    true,表示清空聊天窗口
 *    false, 表示正常刷写
 */
void showChat(bool isRefresh) {
  //消息总行数
  int len = messages.size();

  int i = 0;
  if (len > line_show_num)
    i = len - line_show_num;
  int line_num = len - i;
  int cur_line = 1;

  //更新消息总高度以加快前期消息更新速度
  total_msg_height = len * char_height;
  if (total_msg_height >= tft.height() - 34)
    total_msg_height = tft.height() - 34;
  //重置聊天框
  //  tft.fillRect(7, tft.height() - total_msg_height - 12, tft.width() - 14,
  //  total_msg_height, ILI9341_BLACK);

  for (; i < len; i++) {
    String cur_msg = messages.at(i);
    int text_len = cur_msg.length();
    int text_width = char_width * text_len;
    int text_height = char_height * (line_num - cur_line + 1);
    cur_line++;
    int x = 0;
    int y = tft.height() - text_height - 10;
    if (chat_modes.at(i) == 0) {
      x = tft.width() / 2 - text_width / 2;
      tft.setTextColor(ILI9341_YELLOW);
    } else //如果是系统提示信息，应该居中，不考虑超出范围（不允许）
        if (chat_modes.at(i) == 1) {
      x = 10;
      tft.setTextColor(ILI9341_GREEN);
    } else //如果是系统提示信息，应该居中，不考虑超出范围（不允许）
        if (chat_modes.at(i) == 2) {
      x = tft.width() - text_width - 10;
      tft.setTextColor(ILI9341_RED);
    }
    if (isRefresh)
      tft.setTextColor(ILI9341_BLACK);
    tft.setCursor(x, y);
    tft.setTextSize(1);
    tft.println(cur_msg);
  }
}

/*
 * 显示聊天信息
 *
 * chat_mode:
 *  0: 系统提示信息
 *  1: 我方信息
 *  2: 对方信息
 *
 * text：
 *  聊天信息显示，自动换行等逻辑在内部处理
 */
void addChat(int msg_mode, String text) {
  //文本长度
  int text_line = text.length();
  //文本行数计算
  int lineNum = text_line / line_max_length + 1;
  //若行数刚刚好，则无需+1
  if (text_line % line_max_length == 0)
    lineNum--;
  //重置聊天框
  showChat(true);

  //把内容切割放入messages中
  for (int i = 0; i < lineNum; i++) {
    chat_modes.push_back(msg_mode);
    if (text.length() > line_max_length) {
      messages.push_back(text.substring(0, line_max_length));
    } else {
      messages.push_back(text);
    }
    text = text.substring(line_max_length);
  }
  //更新聊天窗口
  showChat(false);
}

void send_packet_from_serial(char remoteIP[], unsigned int remotePort,
                             String data) {
  Serial.println(data);
  Udp.beginPacket(remoteIP, remotePort);
  Udp.print(data);
  Udp.endPacket();
  addChat(1, data);
}

void blink() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
  }
}
void connect() { Serial.println("Connection establishment"); }

void open() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(STASSID, STAPSK);
  state = WIFI_UP;
}

void close() {
  Serial.println("WIFI closed");
  WiFi.mode(WIFI_OFF);
  state = WIFI_DOWN;
}

void loop() {
  int op = 0;
  String data = "";

  if (state == WIFI_DOWN && digitalRead(key0) == HIGH)
    open();
  if (state != WIFI_DOWN && digitalRead(key1) == HIGH)
    close();

  if (Serial.available()) {
    String data = Serial.readString();
    Serial.println(data.substring(0, OPTION));
    if (data.substring(0, OPTION) == "WIFI") {
      if (data.substring(5, 7) == "ON") {
        op = 1;
        if (state == WIFI_DOWN) {
          open();
        }
      } else if (data.substring(5, 7) == "OF") {
        op = 2;
        if (state != WIFI_DOWN) {
          close();
        }
      }
      delay(20);
    } else if (data.substring(0, OPTION) == "DATA") {
      op = 3;
      data = data.substring(5);
      send_packet_from_serial(remoteIP, remotePort, data);
      delay(20);
    }
  }

  switch (state) {
  case WIFI_DOWN:
    if (preState != WIFI_DOWN) {
      changeConState(state, "WIFI close");
      addChat(0, "WIFI close");
      preState = WIFI_DOWN;
    }
    break;
  case WIFI_UP:
    if (preState != WIFI_UP) {
      changeConState(state, "no connection");
      if (preState == WIFI_CONNECT) {
        // alert connection loss
        addChat(0, "connection loss");
      } else if (preState == WIFI_DOWN) {
        addChat(0, "WIFI open");
      }
      preState = WIFI_UP;
    }

    // wait for connection
    if (WiFi.status() == WL_CONNECTED) {
      Serial.print("Connected! IP address: ");
      Serial.println(WiFi.localIP());
      state = WIFI_CONNECT;
    }

    break;
  case WIFI_CONNECT:
    if (preState != WIFI_CONNECT) {
      changeConState(state, "connected");
      addChat(0, "connection establish");
      preState = WIFI_CONNECT;
    }

    // check if the remote loss
    if (WiFi.status() != WL_CONNECTED) {
      state = WIFI_UP;
      break;
    }

    // check if there's packet send
    int packetSize = Udp.parsePacket();
    if (packetSize) {
      int n = Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
      packetBuffer[n] = 0;
      addChat(2, packetBuffer);
    }

    // if there's serial send
    // if there's data available from serial

    break;
  }
  if (state == WIFI_DOWN)
    ledState = HIGH;
  else if (state == WIFI_UP)
    blink();
  else
    ledState = LOW;
  digitalWrite(LED_BUILTIN, ledState);
}
